/*
 * Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { log } from './logging';
import { LogVerbosity } from './constants';
import { getDefaultAuthority } from './resolver';
import * as logging from './logging';
import {
  SubchannelAddress,
  isTcpSubchannelAddress,
  subchannelAddressToString,
} from './subchannel-address';
import { ChannelOptions } from './channel-options';
import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser';
import { DEFAULT_PORT } from './resolver-dns';
import { ConnectionOptions, Socket } from './typeDeclaration'
import buffer from '@ohos.buffer';
import * as http from './wrapper/http'
import { tls } from './wrapper/tls';
import { URL } from './wrapper/url';
import { proc } from './wrapper/process'
import Url from '@ohos.url'

const TRACER_NAME = 'proxy';

function trace(text: string): void {
  logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text);
}

interface ProxyInfo {
  address?: string;
  creds?: string;
}

function getProxyInfo(): ProxyInfo {
  let proxyEnv = '';
  let envVar = '';
  /* Prefer using 'grpc_proxy'. Fallback on 'http_proxy' if it is not set.
   * Also prefer using 'https_proxy' with fallback on 'http_proxy'. The
   * fallback behavior can be removed if there's a demand for it.
   */

  let grpc_proxy = proc.pro.getEnvironmentVar("grpc_proxy");
  let https_proxy = proc.pro.getEnvironmentVar("https_proxy");
  let http_proxy = proc.pro.getEnvironmentVar("http_proxy");
  if (grpc_proxy) {
    envVar = 'grpc_proxy';
    proxyEnv = grpc_proxy;
  } else if (https_proxy) {
    envVar = 'https_proxy';
    proxyEnv = https_proxy;
  } else if (http_proxy) {
    envVar = 'http_proxy';
    proxyEnv = http_proxy;
  } else {
    return {};
  }
  let proxyUrl:URL;
  try {
    proxyUrl = Url.URL.parseURL(proxyEnv);
  } catch (e) {
    log(LogVerbosity.ERROR, `cannot parse value of "${envVar}" env var`);
    return {};
  }
  if (proxyUrl.protocol !== 'http:') {
    log(
      LogVerbosity.ERROR,
      `"${proxyUrl.protocol}" scheme not supported in proxy URI`
    );
    return {};
  }
  let userCred: string | null = null;
  if (proxyUrl.username) {
    if (proxyUrl.password) {
      log(LogVerbosity.INFO, 'userinfo found in proxy URI');
      userCred = decodeURIComponent(`${proxyUrl.username}:${proxyUrl.password}`);
    } else {
      userCred = proxyUrl.username;
    }
  }
  const hostname = proxyUrl.hostname;
  let port = proxyUrl.port;
  /* The proxy URL uses the scheme "http:", which has a default port number of
   * 80. We need to set that explicitly here if it is omitted because otherwise
   * it will use gRPC's default port 443. */
  if (port === '') {
    port = '80';
  }
  const result: ProxyInfo = {
    address: `${hostname}:${port}`,
  };
  if (userCred) {
    result.creds = userCred;
  }
  trace(
    'Proxy server ' + result.address + ' set by environment variable ' + envVar
  );
  return result;
}

function getNoProxyHostList(): string[] {
  /* Prefer using 'no_grpc_proxy'. Fallback on 'no_proxy' if it is not set. */
  let noProxyStr = proc.pro.getEnvironmentVar("no_grpc_proxy");
  let envVar = 'no_grpc_proxy';
  if (!noProxyStr) {
    noProxyStr = proc.pro.getEnvironmentVar("no_proxy");
    envVar = 'no_proxy';
  }
  if (noProxyStr) {
    trace('No proxy server list set by environment variable ' + envVar);
    return noProxyStr.split(',');
  } else {
    return [];
  }
}

export interface ProxyMapResult {
  target: GrpcUri;
  extraOptions: ChannelOptions;
}

export function mapProxyName(
  target: GrpcUri,
  options: ChannelOptions
): ProxyMapResult {
  const noProxyResult: ProxyMapResult = {
    target: target,
    extraOptions: {},
  };
  if ((options['grpc.enable_http_proxy'] ?? 1) === 0) {
    return noProxyResult;
  }
  if (target.scheme === 'unix') {
    return noProxyResult;
  }
  const proxyInfo = getProxyInfo();
  if (!proxyInfo.address) {
    return noProxyResult;
  }
  const hostPort = splitHostPort(target.path);
  if (!hostPort) {
    return noProxyResult;
  }
  const serverHost = hostPort.host;
  for (const host of getNoProxyHostList()) {
    if (host === serverHost) {
      trace(
        'Not using proxy for target in no_proxy list: ' + uriToString(target)
      );
      return noProxyResult;
    }
  }
  const extraOptions: ChannelOptions = {
    'grpc.http_connect_target': uriToString(target),
  };
  if (proxyInfo.creds) {
    extraOptions['grpc.http_connect_creds'] = proxyInfo.creds;
  }
  return {
    target: {
      scheme: 'dns',
      path: proxyInfo.address,
    },
    extraOptions: extraOptions,
  };
}

export interface ProxyConnectionResult {
  socket?: Socket;
  realTarget?: GrpcUri;
}

export function getProxiedConnection(
  address: SubchannelAddress,
  channelOptions: ChannelOptions,
  connectionOptions: ConnectionOptions
): Promise<ProxyConnectionResult> {
  return Promise.resolve<ProxyConnectionResult>({});
}